<?php
/*
	PHPSambaExplorer
	Copyright 2002-2003 Joel Goben
	jgoben@users.sourceforge.net

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You may obtain a copy of the GNU General Public License at
	http://www.gnu.org/licenses/gpl.html or, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
require("phpsamba_conf.php");
define("SCRIPTNAME",basename($PHP_SELF));
//----------------------------------------------------------------------------------------
// HTTP Post & Get Variable Parsing
//----------------------------------------------------------------------------------------
session_cache_limiter('public');  //Note: this line avoids a bug in InternetExplorer that won't allow downloads over https
session_register("session");
if(!isset($session))
{
	$session = new SambaClass;
	$session->ChangePath("\\\\".DEFAULT_SERVER."\\".DEFAULT_SHARE);
}

if($_REQUEST["path"])
	$session->ChangePath($_REQUEST["path"]);
if($_POST["user"] || $_POST["pass"])
	$session->Logon($_POST["user"],$_POST["pass"]);
if($_POST["logout"]==1)
	$session->Logoff();

//----------------------------------------------------------------------------------------
// Page Display Starts Here
//----------------------------------------------------------------------------------------
if(empty($session->server) || (empty($session->user) && empty($session->pass)))
	$session->Auth($_POST["path"],$_POST["user"]);
else if($session->file != "" &&	$session->DownloadFile($session->file));
else
{
	ob_start();
	$session->Head();
	$session->UploadFile($HTTP_POST_FILES[uploadedfile]);
	echo "<table width=\"100%\" cellspacing=0 cellpadding=0 border=0 bgcolor=\"".FRAMECOLOR."\">";
	echo "<tr><td colspan=5>";$session->ScanDir();echo "</td></tr>";
	echo "<tr>";
	echo "<td width=10>&nbsp</td>";
	echo "<td colspan=3>";$session->ToolBar();echo "</td>";
	echo "<td width=10>&nbsp</td>";
	echo "</tr>";	
	echo "<tr>";
	echo "<td width=10>&nbsp</td>";
	echo "<td colspan=3><font color=\"".ERRORCOLOR."\">";$session->DisplayErrors();echo "&nbsp</font></td>";
	echo "<td width=10>&nbsp</td>";
	echo "</tr>";
	echo "<tr valign=top>";
	echo "<td width=10>&nbsp</td>";
	echo "<td width=\"20%\" bgcolor=\"".BGCOLOR."\">";$session->ListDirTree();echo "</td>";
	echo "<td width=10>&nbsp</td>";
	echo "<td width=\"90%\" bgcolor=\"".BGCOLOR."\">";$session->ListDir();echo "</td>";
	echo "<td width=10>&nbsp</td>";
	echo "</tr>";
	echo "<tr><td>&nbsp</td><td align=right colspan=3><font size=-4><a href=\"http://phpsamba.sourceforge.net\">PHPSambaExplorer</a> by Joel Goben - Beta v0.85</font></td><td>&nbsp</td></tr>";
	echo "</table>\n";
	$session->Tail();
	ob_end_flush();
}

//----------------------------------------------------------------------------------------
// SambaClass
//----------------------------------------------------------------------------------------
class SambaClass
{
	var $server;
	var $share;
	var $path;
	var $user;
	var $pass;
	var $error;
	var $DirTree;
	var $CurDirList;

	function Head()
	{
		echo "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n";
		echo "<html>\n";
		echo "<head>\n";
		echo "<title>PHPSambaExplorer</title>\n";
		echo "<style type=\"text/css\"><!--BODY { margin-top: 0; margin-left: 0; margin-right: 0;} A:hover {text-decoration: underline;}A {text-decoration: none;}--></style>\n";
		echo "</head>\n";
		echo "<body text=\"".TEXTCOLOR."\" bgcolor=\"".BGCOLOR."\" link=\"".LINKCOLOR."\" alink=\"".LINKCOLOR."\" vlink=\"".LINKCOLOR."\">\n";
	}

	function Tail()
	{
		echo "</body>\n</html>";
	}

	function Logon($user,$pass)
	{
		if(empty($this->server))
			return;
		$userOk = False;
		global $UserAllow;
		global $UserDeny;
		if(isset($UserAllow))
		{
			foreach($UserAllow as $name)
				if($user == $name)
				{
					$userOk = True;
					break;
				}
		}
		else
			$userOk = True;
		if(isset($UserDeny))
		{
			foreach($UserDeny as $name)
				if($user == $name)
				{
					$userOk = False;
					break;
				}
		}
		if($userOk == True)
		{
			$this->user = $user;
			$this->pass = $pass;
			if($this->SMBCommand("pwd"))
				$this->Log("CONNECTED: \\\\$this->server\\$this->share");
		}
		else
		{
			$this->Error("\"$user\" is not a allowed to use this system");
			$this->Log("ERROR: $user is not allowed to use this system");
		}
	}

	function Logoff()
	{
		$this->head();
		echo "<center><table height=75%><tr><td align=center valign=middle>";
		echo "You are now logged out<br><a href=\"".SCRIPTNAME."\">Log back in</a>";
		echo "</td></tr></table></center>";
		$this->Log("DISCONNECTED");
		session_destroy();
		$this->tail();
		exit();
	}

	function ChangePath($path)
	{
		$path=str_replace("\'","'",str_replace("\\\\","\\",str_replace("/","\\",$path)));
		preg_match("/\\\*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|[\w\d]{1,15})\\\([\w\d]{1,12}).{0,1}(.*?)([^\\\]*$)/",$path,$match);
		$serverOk = True;
		$old_server = $this->server;
		$old_share = $this->share;
		$this->server = $match[1];
		$this->share = $match[2];
		if($this->server != $old_server && $this->server != "")
		{
			//TODO: use nmblookup to find if this host exists
			unset($this->DirTree);
			$serverOk = False;
			global $ServerAllow;
			global $ServerDeny;
			if(isset($ServerAllow))
			{
				foreach($ServerAllow as $validServer)
					if($this->server == $validServer)
						$serverOk = True;
			}
			else
				$serverOk = True;
			if(isset($ServerDeny))
				foreach($ServerDeny as $invalidServer)
					if($this->server == $invalidServer)
						$serverOk = False;
			if($serverOk == False)
			{
				$this->Error("You are not allowed to access $this->server from this system");
				$this->server = $old_server;
				$this->share = $old_share;
			}
		}
		if($serverOk == True)
		{
			$this->path = $match[3];
			$this->file = $match[4];
			//TODO: if a file is actually a directory without a trailing slash, add the trailing slash
			$this->path = preg_replace("/[^\\\]*[\\\]{0,1}\.\.\\\/","",$this->path);
		}
	}

	function ScanDir()
	{
		if(!is_array($this->DirTree) || $_POST["refresh"])
			$this->DirTree=array();
		$this->DirTree = $this->ScanDirR($this->DirTree,".\\".$this->path,"");
	}
	function ScanDirR($tree,$path,$buildpath)
	{
		preg_match("/(^.[^\\\]*)(.*)/",$path,$match);
		$buildpath .= $match[1];
		$path = $match[2];
		if(empty($tree))
		{
			$ls_array = explode("\n",$this->SMBCommand("cd \\\"$buildpath\\\"; ls"));
			foreach($ls_array as $val)
			{
				if(preg_match("/^  (.*\S)\s+(\d+)\s+(\w\w\w \w\w\w .. ..:..:.. \d+)$/",$val,$match))
				{
					$file = ":$match[1]";
					$fsize = $match[2];
					$fmod = preg_replace("/ (\d) /"," 0$1 ",$match[3],1);
					if(preg_match("/(^.*?\S)\s+([RSHAD]*$)/",$file,$match))  // Get any atributes out of the file name
					{
						$file = $match[1];
						$ftype = $match[2];
					}
					else
						$ftype = "?";
					if($file!=":." && $file!=":..")
						$tree = array_merge($tree,array($file => array("ftype"=>$ftype,"fsize"=>$fsize,"fmod"=>$fmod)));
				}
			}
			@uksort($tree,create_function('$a,$b','return(strcasecmp($a,$b));'));	// Case insensative key sort
		}
		preg_match("/^.([^\\\]*)/",$path,$match);
		$subdir = $match[1];
		if($path=="\\")
		{
			$this->CurDirList = $tree;
			return($tree);
		}
		else
			return(array_merge($tree,array("?".$subdir => $this->ScanDirR($tree["?".$subdir],$path,$buildpath))));
	}

	function ListDirTree()
	{
		echo "<table cellspacing=0 cellpadding=0 border=0>";
		echo "<tr><td><img src=\"images/dir.png\" alt=DIR> <a href=\"".SCRIPTNAME."?path=\\\\$this->server\\$this->share\\\">\\\\$this->server\\$this->share</a></td></tr>";
		$pathElements = explode("\\",$this->path);
		$this->ListDirTreeR($this->DirTree,$pathElements,"",1);
		echo "</table>";
	}
	function ListDirTreeR($tree,$path,$buildPath,$count)
	{
		$dir = array_shift($path);
		if(!is_array($tree))
			return;
		foreach($tree as $name => $inf)
		{
			$name = ltrim($name,":");
			if(strstr($inf[ftype],"D"))
			{
				$pathstring = $this->Href("$buildPath\\$name\\");
				echo "<tr><td nowrap>";
				$i=$count;
				while($i-- > 0)
				{
					if($i != 0)
						echo "<img src=\"images/line.png\" alt=\"|\">";
					else
						echo "<img src=\"images/angle.png\" alt=\"+\">";
				}
				if($name != $dir)
				{
					echo "<img src=\"images/dir.png\" alt=DIR> <a href=\"$pathstring\">$name</a></td></tr>";
				}
				else
				{
					if(count($path)==1)
						echo "<img src=\"images/opendir.png\" alt=DIR> <a href=\"$pathstring\"><b>$name</b></a></td></tr>";
					else
						echo "<img src=\"images/dir.png\" alt=dir> <a href=\"$pathstring\">$name</a></td></tr>";
					$this->ListDirTreeR($tree["?".$dir],$path,$buildPath."\\".$dir,$count+1);
				}
			}
		}
	}

	function ListDir()
	{
		if(!is_array($this->CurDirList))
		{
			echo "<center>Empty</center>";
			return;
		}
		echo "<table><tr align=left><th></th><th>Name</th><th>Size</th><th>Modified</th></tr>";
		$fsizetypename = array("","KB","MB","GB");
		foreach($this->CurDirList as $name => $inf)
		{
			$name = ltrim($name,":");
			$pathstring = $this->Href("\\$this->path$name");
			if(strstr($inf[ftype],"D"))
			{
				echo "<tr><td><img src=\"images/dir.png\" alt=DIR></td>";
				echo "<td><a href=\"$pathstring\\\">$name</a></td>";
				echo "<td></td><td><tt>$inf[fmod]</tt></td></tr>";
			}
			else if($name[0]!="?")
			{
				echo "<tr><td><img src=\"images/file.png\" alt=FILE></td>";
				echo "<td><a href=\"$pathstring\">$name</a></td>";
				$fsizetype=0;
				while($inf[fsize] >= 1024 && type < 4)
				{
					$inf[fsize] = $inf[fsize] / 1024;
					$fsizetype+=1;
				}
				if($inf[fsize] > 0)
					echo "<td align=right><tt>".round($inf[fsize])."$fsizetypename[$fsizetype]</tt></td>";
				else
					echo "<td></td>";
				echo "<td><tt>$inf[fmod]</tt></td></tr>";
			}
		}
		echo "</table>";
	}

	function ToolBar()
	{
		echo "<table cellspacing=5 cellpadding=0 border=0><tr>";
		echo "<td><form action=\"".SCRIPTNAME."\" method=post enctype=\"multipart/form-data\">";
		echo "<input type=submit value=\"Upload Selected File\"> <input type=file name=uploadedfile><input type=hidden name=refresh value=1></form></td>";
		echo "<td>&nbsp</td>";
		echo "<td><form action=\"".SCRIPTNAME."\" method=post><input type=hidden name=refresh value=1><input type=submit value=\"Refresh\"></form></td>";
		echo "<td><form action=\"".SCRIPTNAME."\" method=post><input type=hidden name=logout value=1><input type=submit value=\"Log Out\"></form></td>";
		echo "</tr></table>";
		echo "<form action=\"".SCRIPTNAME."\" method=get><b>Address </b><input type=text name=path value=\"\\\\$this->server\\$this->share\\$this->path\" size=80></form>";
	}

	function Auth($path="path",$user="user")
	{
		if(!empty($this->server))
		{
			$path = "\\\\$this->server\\";
			if(!empty($this->share))
				$path .= "$this->share\\";
		}
		if(!empty($this->user))
			$user = $this->user;
		$this->Head();	
		echo "<center><table height=75%><tr><td align=center valign=middle>";
		$this->DisplayErrors();
		echo "<table><tr><th>".LOGIN_MESSAGE."</th></tr><tr><td bgcolor=".FRAMECOLOR.">";
		echo "<table><form action=\"".SCRIPTNAME."\" method=post>";
		echo "<tr><td>Server</td><td><input type=text name=path value=\"$path\"></td></tr>";
		echo "<tr><td>Username</td><td><input type=text name=user value=\"$user\"></td></tr>";
		echo "<tr><td>Password</td><td><input type=password name=pass></td></tr>";
		echo "<tr><td colspan=2 align=right><input type=submit value=Connect></td></tr>";
		echo "</form></table>";

		echo "</td></tr></table>";
		echo "</td></tr></table></center>";
		$this->Tail();
	}

	function UploadFile($file)
	{
		if(!isset($file)||$file[name]=="")
			return(false);
		if($this->SMBCommand("put \\\"$file[tmp_name]\\\" \\\"$this->path$file[name]\\\"")!=False)
		{
			$this->Error("Uploaded ".$file[name]);
			$this->Log("UL:\\\\$this->server\\$this->share\\$this->path$file[name]");
		}
		return(true);
	}

	function DownloadFile($filename)	//TODO: guess MIME type by file extension, send application/octet-stream only as a last resort.
	{
		$randname = TMP_FILE_DIR."/phpsamba_".md5(uniqid(""));
		touch($randname);
		chmod($randname,0600);
		if($this->SMBCommand("get \\\"$this->path$filename\\\" \\\"$randname\\\""))
		{
			header("Content-type: application/octet-stream");
			header("Content-Disposition: attachment; filename=$filename");
			readfile($randname);
			unlink($randname);
			$this->Log("DL:\\\\$this->server\\$this->share\\$this->path$filename");
			return(True);
		}
		unlink($randname);
		return(False);
	}

	function Error($message,$log=False)
	{
		if(is_array($this->error))
			array_push($this->error,$message);
		else
			$this->error=array($message);
		if($log==True)
			$this->Log($message);
	}

	function DisplayErrors()
	{
		if(!is_array($this->error))
			return;
		foreach($this->error as $message)
			echo "<b>$message</b><br>";
		unset($this->error);
	}	
	
	function Log($message)
	{
		if(!defined("LOG_FILE"))
			return;
		if(getenv(HTTP_X_FORWARDED_FOR))
			$ip=getenv(HTTP_X_FORWARDED_FOR);
		else
			$ip=getenv(REMOTE_ADDR);
		$buffer = date("m/d/y G:i:s")." $ip $this->user ".$message."\n";
		//NOTE: If another instance of PHPSambaExplorer is using the log file we can't log.
		//Decide what we want to write before we open it, to keep it open the min amount of time.
		$fp=@fopen(constant("LOG_FILE"),"a");
		if($fp)
		{
			fwrite($fp,$buffer);
			fclose($fp);
		}
		else
			$this->Error("ERROR: Can not write to log file, if the problem persists please contact you SysAdmin, as this error can't be logged!");
	}

	function SMBCommand($command)
	{
		// Make sure there is an even number of quotes so $command can never be of the form ";DoSomethingEvil
		$quotes = substr_count($command,"\"")/2;
		if($quotes != round($quotes))
		{
			$this->Error("Malformed SMBCommand");
			$this->Log("$Malformed SMBCommand: $command");
			return(False);
		}
		// User environment variables for user & pass, using the command line would be insecure
		putenv("USER=$this->user%$this->pass");
		$result = `smbclient //$this->server/$this->share -N -c "$command"`;
		if(preg_match("/Connection to .*? failed/",$result))
		{
			$this->Error("Could not connect to $this->server");
			return(False);
		}
		if(preg_match("/ERRSRV|ERRDOS/",$result))
		{
			if(preg_match("/ERRDOS - ERRnoaccess/",$result))
			{
				$this->Error("Your current account does not have access to this resource, please login again with another account");
				$this->Log("ERRnoaccess: \\\\$this->server\\$this->share\\$this->path$");
			}
			if(preg_match("/ERRSRV - ERRbadpw/",$result))
			{
				$this->Error("Login Failed, Please try again");
				$this->Log("ERRbadpw: invalid passwd for \\\\$this->server\\$this->share");
				ob_end_clean();
				$this->Auth();
				exit();
			}
			else if(preg_match("/ERRDOS - ERRbadfile/",$result))
				$this->Error("File does not exist, or you lack permission to view it");
			else if(preg_match("/ERRDOS - ERRbadpath/",$result))
				$this->Error("Directory does not exist, or you lack permission to view it");
			else if(preg_match("/ERRDOS - ERRnosuchshare/",$result))
				$this->Error("Network share does not exist");
			else if(preg_match("/ERRSRV - ERRinvnetname/",$result))
				$this->Error("Invalid Network Name");
			else
			{
				$this->Error("An unknown error occured durring command<b>$command</b><br><form><textarea cols=80 rows=10>$result</textarea></form><br>");
				$this->Log("UnknownError $result");
			}
			return(False);
		}
		return($result);
	}

	function Href($string)
	{
		return(str_replace(" ","+",str_replace("+","%2B",str_replace("%","%25",SCRIPTNAME."?path=\\\\$this->server\\$this->share".$string))));
	}
}// END OF SambaClass
?>